﻿/*

The MIT License

Copyright (c) 2010 Cartesian Analytics, Inc. 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

using System;

namespace Pipra.Math
{
    /// <summary>
    /// A square matrix with two rows and two columns with an element type of double.
    /// </summary>
    public class MatrixOrder2 :
        IMatrixOrder2<double>,
        IEquatable<MatrixOrder2>
    {
        /// <summary>
        /// Determines if two matrices are equal.
        /// </summary>
        /// <param name="left">A matrix.</param>
        /// <param name="right">A matrix.</param>
        /// <returns>True when equal.</returns>
		public static bool operator ==(MatrixOrder2 left, MatrixOrder2 right) {
			return (ReferenceEquals(null, left) ? ReferenceEquals(null, right) : left.Equals(right));
		}

        /// <summary>
        /// Determines if two matrices are not equal.
        /// </summary>
        /// <param name="left">A matrix.</param>
        /// <param name="right">A matrix.</param>
        /// <returns>True when not equal.</returns>
		public static bool operator !=(MatrixOrder2 left, MatrixOrder2 right) {
			return (ReferenceEquals(null, left) ? !ReferenceEquals(null, right) : !left.Equals(right));
		}

        /// <summary>
        /// Implements the operator *.
        /// </summary>
        /// <param name="left">The left matrix.</param>
        /// <param name="right">The right matrix.</param>
        /// <returns>The result of multiplying the <paramref name="left"/> matrix by the <paramref name="right"/> matrix.</returns>
		public static MatrixOrder2 operator *(MatrixOrder2 left, MatrixOrder2 right) {
			return left.Multiply(right);
		}

		/// <summary>
		/// The element at row 0 and column 0.
		/// </summary>
		public double E00;
		/// <summary>
		/// The element at row 0 and column 1.
		/// </summary>
		public double E01;
		/// <summary>
		/// The element at row 1 and column 0.
		/// </summary>
		public double E10;
		/// <summary>
		/// The element at row 1 and column 1.
		/// </summary>
		public double E11;

		/// <summary>
		/// Constructs a new identity matrix.
		/// </summary>
		public MatrixOrder2() {
			SetIdentity();
		}

		/// <summary>
		/// Constructs a new matrix with the given element values.
		/// </summary>
		/// <param name="e00">The value for the element at 0,0.</param>
		/// <param name="e01">The value for the element at 0,1.</param>
		/// <param name="e10">The value for the element at 1,0.</param>
		/// <param name="e11">The value for the element at 1,1.</param>
		public MatrixOrder2(
			double e00, double e01,
			double e10, double e11
		) {
			E00 = e00;
			E01 = e01;
			E10 = e10;
			E11 = e11;
		}

		/// <summary>
		/// Copies the element values from the given matrix.
		/// </summary>
		/// <param name="m">A matrix to copy from.</param>
		public MatrixOrder2(MatrixOrder2 m) {
			E00 = m.E00;
			E01 = m.E01;
			E10 = m.E10;
			E11 = m.E11;
		}

		/// <summary>
		/// Copies the element values from the given matrix.
		/// </summary>
		/// <param name="m">A matrix to copy from.</param>
        public MatrixOrder2(IMatrixOrder2<double> m)
        {
			E00 = m.E00;
			E01 = m.E01;
			E10 = m.E10;
			E11 = m.E11;
		}

		/// <summary>
		/// The element at row 0 and column 0.
		/// </summary>
		double IMatrixOrder2<double>.E00 {
			get { return E00; }
			set { E00 = value; }
		}

		/// <summary>
		/// The element at row 0 and column 1.
		/// </summary>
        double IMatrixOrder2<double>.E01
        {
			get { return E01; }
			set { E01 = value; }
		}

		/// <summary>
		/// The element at row 1 and column 0.
		/// </summary>
        double IMatrixOrder2<double>.E10
        {
			get { return E10; }
			set { E10 = value; }
		}

		/// <summary>
		/// The element at row 1 and column 1.
		/// </summary>
        double IMatrixOrder2<double>.E11
        {
			get { return E11; }
			set { E11 = value; }
		}

		/// <summary>
		/// Sets all elements in the matrix.
		/// </summary>
		/// <param name="e00">The value for the element at 0,0.</param>
		/// <param name="e01">The value for the element at 0,1.</param>
		/// <param name="e10">The value for the element at 1,0.</param>
		/// <param name="e11">The value for the element at 1,1.</param>
		public void SetElements(
			double e00, double e01,
			double e10, double e11
		) {
			E00 = e00;
			E01 = e01;
			E10 = e10;
			E11 = e11;
		}

        /// <summary>
        /// Sets the elements of the matrix to that of the identity matrix.
        /// </summary>
		public void SetIdentity() {
			E00 = E11 = 1.0;
			E01 = E10 = 0;
		}

        /// <summary>
        /// Calculates the determinant of the matrix.
        /// </summary>
        /// <returns>The determinant.</returns>
		public double GetDeterminant() {
			return (E00 * E11) - (E10 * E01);
		}

        /// <summary>
        /// Inverts the matrix.
        /// </summary>
		public void Invert() {
			double det = GetDeterminant();
			if (0 == det || Double.IsNaN(det)) {
				throw new InvalidOperationException();
			}
			double tmp = E00;
			E00 = E11 / det;
			E11 = tmp / det;
			tmp = -det;
			E01 = E01 / tmp;
			E10 = E10 / tmp;
		}

        /// <summary>
        /// Generates a matrix which is the inverse.
        /// </summary>
        /// <returns>The inverse of the matrix.</returns>
		public MatrixOrder2 GetInverse() {
			double det = GetDeterminant();
            if (0 == det || Double.IsNaN(det))
            {
				throw new InvalidOperationException();
			}
			double negDet = -det;
            return new MatrixOrder2(
				E11 / det,
				E01 / negDet,
				E10 / negDet,
				E00 / det
			);
		}

		IMatrixSquare<double> IMatrixSquare<double>.GetInverse() {
			return GetInverse();
		}

		/// <summary>
		/// Transposes this matrix.
		/// </summary>
		public void Transpose() {
			double tmp = E01;
			E01 = E10;
			E10 = tmp;
		}

		/// <summary>
		/// Generates a new matrix which is the transpose of this matrix.
		/// </summary>
		/// <returns>The transpose of this matrix.</returns>
        public MatrixOrder2 GetTransposed()
        {
            return new MatrixOrder2(
			   E00, E10,
			   E01, E11
		   );
		}

		/// <summary>
		/// Generates a new matrix which is the transpose of this matrix.
		/// </summary>
		/// <returns>The transpose of this matrix.</returns>
		IMatrixSquare<double> IMatrixSquare<double>.GetTransposed() {
			return GetTransposed();
		}

		/// <summary>
		/// The number of rows in this matrix.
		/// </summary>
		int IMatrix<double>.RowCount {
            get { return 2; }
		}

		/// <summary>
		/// The number of columns in this matrix.
		/// </summary>
        int IMatrix<double>.ColumnCount
        {
			get { return 2; }
		}

        /// <summary>
        /// Gets the order of the square matrix.
        /// </summary>
        public int Order
        {
            get { return 2; }
        }

		/// <summary>
		/// The total number of elements in this matrix.
		/// </summary>
		public int ElementCount {
			get { return 4; }
		}

		/// <summary>
		/// Retreives the element at the given row and column location.
		/// </summary>
		/// <param name="r">The row.</param>
		/// <param name="c">The column.</param>
		/// <returns>The element at the given location.</returns>
		public double Get(int r, int c) {
			if (c == 0 || c == 1)
			{
			    if (0 == r) {
					return 0 == c ? E00 : E01;
				}
			    if (1 == r) {
			        return 0 == c ? E10 : E11;
			    }
			    throw new ArgumentOutOfRangeException("r");
			}
			throw new ArgumentOutOfRangeException("c");			
		}

		/// <summary>
		/// Sets the element at the given row and column to the given <paramref name="value"/>.
		/// </summary>
		/// <param name="r">The row.</param>
		/// <param name="c">The column.</param>
		/// <param name="value">The value to store in the matrix element.</param>
		public void Set(int r, int c, double value) {
			if (c == 0 || c == 1) {
				if (0 == r) {
					if (0 == c) {
						E00 = value;
					}
					else {
						E01 = value;
					}
				}
				else if (1 == r) {
					if (0 == c) {
						E10 = value;
					}
					else {
						E11 = value;
					}
				}
				else {
					throw new ArgumentOutOfRangeException("r");
				}
			}
			else {
				throw new ArgumentOutOfRangeException("c");
			}
		}

		/// <summary>
		/// Returns a hash code for this instance.
		/// </summary>
		/// <returns>
		/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
		/// </returns>
		public override int GetHashCode() {
			return E00.GetHashCode();
		}

		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="System.String"/> that represents this instance.
		/// </returns>
		public override string ToString() {
			return String.Concat(
				E00, ',', E01, ",\n",
				E10, ',', E11
			);
		}
        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
		public override bool Equals(object obj)
        {
            return null != obj
                && (
                    (obj is MatrixOrder2 && Equals(obj as MatrixOrder2))
                )
            ;
        }

        /// <summary>
        /// Indicates whether another matrix is equal to this instance.
        /// </summary>
        /// <param name="other">A matrix to compare.</param>
        /// <returns><see langword="true"/> when the given matrix is equal to this instance.</returns>
        public bool Equals(MatrixOrder2 other)
        {
			return !ReferenceEquals(null,other)
				&& E00 == other.E00
				&& E01 == other.E01
				&& E10 == other.E10
				&& E11 == other.E11
			;
		}

        /// <summary>
        /// Multiplies this matrix by another.
        /// </summary>
        /// <param name="right">The right matrix.</param>
        /// <returns>The result of multiplying this matrix by the right matrix (<c>this*<paramref name="right"/></c>).</returns>
        public MatrixOrder2 Multiply(MatrixOrder2 right)
        {
            return new MatrixOrder2(
				(E00 * right.E00) + (E01 * right.E10),
				(E00 * right.E01) + (E01 * right.E11),
				(E10 * right.E00) + (E11 * right.E10),
				(E10 * right.E01) + (E11 * right.E11)
			);
		}
        /// <summary>
        /// Multiplies this matrix by another and stores the result in this matrix (<c>this*=<paramref name="right"/></c>).
        /// </summary>
        /// <param name="right">The right matrix.</param>
        public void MultiplyAssignment(MatrixOrder2 right)
        {
			double t00 = E00;
			E00 = (t00 * right.E00) + (E01 * right.E10);
			E01 = (t00 * right.E01) + (E01 * right.E11);
			double t10 = E10;
			E10 = (t10 * right.E00) + (E11 * right.E10);
			E11 = (t10 * right.E01) + (E11 * right.E11);
		}

        /// <summary>
        /// Multiplies this matrix by another.
        /// </summary>
        /// <param name="right">The right matrix.</param>
        /// <returns>The result of multiplying this matrix by the right matrix (<c>this*<paramref name="right"/></c>).</returns>
        public MatrixOrder2 Multiply(IMatrixOrder2<double> right)
        {
            return new MatrixOrder2(
				(E00 * right.E00) + (E01 * right.E10),
				(E00 * right.E01) + (E01 * right.E11),
				(E10 * right.E00) + (E11 * right.E10),
				(E10 * right.E01) + (E11 * right.E11)
			);
		}

        /// <summary>
        /// Multiplies this matrix by another.
        /// </summary>
        /// <param name="right">The right matrix.</param>
        /// <returns>The result of multiplying this matrix by the right matrix (<c>this*<paramref name="right"/></c>).</returns>
		IMatrixOrder2<double> IMatrixOrder2<double>.Multiply(IMatrixOrder2<double> right) {
			return Multiply(right);
		}

        /// <summary>
        /// Multiplies this matrix by another and stores the result in this matrix (<c>this*=<paramref name="right"/></c>).
        /// </summary>
        /// <param name="right">The right matrix.</param>
		public void MultiplyAssignment(IMatrixOrder2<double> right) {
			double t00 = E00;
			E00 = (t00 * right.E00) + (E01 * right.E10);
			E01 = (t00 * right.E01) + (E01 * right.E11);
			double t10 = E10;
			E10 = (t10 * right.E00) + (E11 * right.E10);
			E11 = (t10 * right.E01) + (E11 * right.E11);
		}

        /// <summary>
        /// Multiplies this matrix by another.
        /// </summary>
        /// <param name="right">The right matrix.</param>
        /// <returns>The result of multiplying this matrix by the right matrix (<c>this*<paramref name="right"/></c>).</returns>
		public IMatrix<double> Multiply(IMatrix<double> right) {
			throw new NotImplementedException();
		}

        /// <summary>
        /// Multiplies this matrix by another and stores the result in this matrix (<c>this*=<paramref name="right"/></c>).
        /// </summary>
        /// <param name="right">The right matrix.</param>
		public void MultiplyAssignment(IMatrix<double> right) {
			throw new NotImplementedException();
		}
    }
}
